// Assumptions: Supports only one client at a time
//

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "ramprakash.Part1Common.h"

int servSockFd;
fd_set writeFds;
struct sockaddr_in servaddr;

int connectToServer(char *ip)
{
    servSockFd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (servSockFd < 0) 
    {
        perror("socket:");
        return errno;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(LISTENING_PORT);
    inet_aton(ip, &servaddr.sin_addr);

    return 0;
}

int getFile(char *inrf, char *inlf)
{
    struct sockaddr_in src;
    msg_t msg;
    socklen_t len = 0;
    FILE *lfd;
    char rf[80], lf[80];

    memset(rf, 0, sizeof(rf));
    memset(lf, 0, sizeof(lf));
    strcpy(lf, inlf);
    strcpy(rf, inrf);

next_try:
    memset(&src, 0, sizeof(src));
    memset(&msg, 0, sizeof(msg));
    msg.cmd = FILE_REQ;
    strcpy(msg.u.f.file, rf);

    if (sendto(servSockFd, &msg, sizeof(msg), 0,
                (const struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("sendto:");
        return errno;
    }

    lfd = fopen(lf, "w+");
    if (!lfd) {
        perror("fopen");
        return errno;
    }

    memset(&msg, 0, sizeof(msg));
    do {
        if ((len = recvfrom(servSockFd, (char *)&msg, sizeof(msg_t), 0, 
                        (struct sockaddr *)&src, 
                        &len)) > 0) {
            LOG("Received: %d (%d) %d\n", 
                    msg.u.f.size, msg.u.f.chunk_size, msg.u.f.offset);
        } else {
            perror("recvfrom");
            return errno;
        }
        if ((msg.cmd == FILE_RESP) && (msg.u.f.size == 0)) {
            printf("Remote file not found. Retry with remote file name:");
            scanf("%s", rf);
            goto next_try;
        }
        if (fwrite(msg.u.f.data, 1, 
                   msg.u.f.chunk_size, lfd) < msg.u.f.chunk_size) {
            perror("fwrite");
            return errno;
        }
    } while ((msg.u.f.chunk_size + msg.u.f.offset) < msg.u.f.size);

    return 0;
}

// 3 tries at getting authenticated to the server with plaintext user/password
int authenticateUser(char *u, char *p)
{
    fd_set readFds;
    struct timeval tv;
    struct sockaddr_in src;
    int rc;
    int try = 0;
    msg_t msg, *pMsg;
    socklen_t len = 0;
    char buf[1024];
    char lu[32];
    char lp[32];

    memset(&lu, 0, sizeof(lu));
    memset(&lp, 0, sizeof(lp));
    strcpy(lu, u);
    strcpy(lp, p);

next_try:
    try++;
    memset(&src, 0, sizeof(src));
    memset(buf, 0, sizeof(buf));
    memset(&msg, 0, sizeof(msg));

    msg.cmd = AUTH_REQ;
    strcpy(msg.u.a.user, lu);
    strcpy(msg.u.a.passwd, lp);

    FD_ZERO(&readFds);
    tv.tv_sec = 2;
    tv.tv_usec = 0;

    if (sendto(servSockFd, &msg, sizeof(msg), 0,
                (const struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) 
    {
        perror("sendto:");
        return errno;
    }

    FD_SET(servSockFd, &readFds);
    rc = select(servSockFd+1, &readFds, NULL, NULL, &tv);
    if (rc == 0) {
        printf("No response from the server. Check if server is running!\n");
        return -1;
    }

    if ((len = recvfrom(servSockFd, buf, 1024, 0, 
                    (struct sockaddr *)&src, 
                    &len)) > 0) {
        pMsg = (msg_t *)buf;
        LOG("Received: %s (%d)\n", pMsg->u.a.user, len);
    } else {
        perror("recvfrom");
        return errno;
    }

    if (pMsg->cmd == AUTH_RESP) {
        if (strcmp(pMsg->u.a.user, "Success") == 0) {
            LOG("Authentication: Success\n");
            return 0;
        }
    }
    if (try < 3) {
        printf("\nAuthentication Failed\n");
        if (getPass(lu, lp) != 0)
            exit(-1);

        goto next_try;
    }
    exit(-1);
}

int main(int argc, char *argv[])
{
    char u[32], p[32];

    if (argc != 4) {
        printf("Usage: udpClnt <server-ip-addr> <full-path-to-remote-file> <local-file>\n");
        return -1;
    }

    if (connectToServer(argv[1]) < 0) {
        printf("Socket creation failed - Exiting\n");
        return -1;
    }

    memset(u, 0, sizeof(u));
    memset(p, 0, sizeof(p));
    if (getPass(u, p) != 0) {
        return -1;
    }
    if (authenticateUser(u, p) < 0) {
        printf("Authentication failed\n");
        return -1;
    }

    if (getFile(argv[2], argv[3]) < 0) {
        printf("Failed to get file\n");
        return -1;
    }

    printf("Retrieved file %s as %s\n", argv[2], argv[3]);

    close(servSockFd);

    return (0);
}
